#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>

#define SERV_PORT 8755
#define MAXLINE 32
#define error_exit(msg) \
	do { perror(msg); exit(EXIT_FAILURE); } while (0)

#define	QSIZE 8		
#define	MAXDG 4096

typedef struct {
	void *dg_data;
	size_t dg_len;
	struct sockaddr *dg_sa;
	socklen_t dg_salen;
} DG;

static DG dg[QSIZE];           // 已收取数据报队列（环形缓冲区）
static long cntread[QSIZE+1];  // 诊断用计数器
static int iget;               // 主循环将处理的下一个数组元素的下标
static int iput;               // 信号处理函数将存放到的下一个数组元素的下标
static int nqueue;             // 队列中供主循环处理的数据报的总数
static socklen_t g_clilen;
static int g_sockfd;

void* Signal(int signo, void (*func)(int)) {
	struct sigaction act, oact;

	act.sa_handler = func; 
	sigemptyset(&act.sa_mask); 
	act.sa_flags = 0;
#ifdef SA_INTERRUPT	
	if (signo == SIGALRM) act.sa_flags |= SA_INTERRUPT;
#endif
#ifdef SA_RESTART 
	if (signo != SIGALRM) act.sa_flags |= SA_RESTART; 
#endif
	if (sigaction(signo, &act, &oact) < 0) 
		return SIG_ERR;
	return oact.sa_handler; // 返回信号的旧行为
}	

static void sig_hup(int signo) {
	int i;
	for (i = 0; i <= QSIZE; i++)
		printf("cntread[%d] = %ld\n", i, cntread[i]);
}		

/* SIGIO信号的信号处理函数 
 * 1.源自Berkeley的实现使用SIGIO信号支持套接字的信号驱动式I/O;
 * 2.UDP上SIGIO信号在如下情况发生：“数据报到达套接字” 和“套接字上发生异步错误”;
 * 3.TCP上SIGIO信号产生过于频繁，近乎无用。 */
static void sig_io(int signo) {
	ssize_t len;
	int nread;
	DG *ptr;

	for (nread = 0; ; ) {
		if (nqueue >= QSIZE) // 检查队列溢出 
			error_exit("receive overflow");

		ptr = &dg[iput];
		ptr->dg_salen = g_clilen;
		len = recvfrom(g_sockfd, ptr->dg_data, MAXDG, 0, ptr->dg_sa, &ptr->dg_salen);
		if (len < 0) {
			if (errno == EWOULDBLOCK)
				break; // all done; no more queued to read 
			else
				error_exit("recvfrom error");
		}
		ptr->dg_len = len;

		nread++;
		nqueue++;
		if (++iput >= QSIZE)
			iput = 0;
	}
	cntread[nread]++; // histogram of # datagrams read per signal 
}

void dg_echo_io(int sockfd_arg, struct sockaddr *pcliaddr, socklen_t clilen_arg) {
	int i;
	const int on = 1;
	sigset_t zeromask, newmask, oldmask;

	g_sockfd = sockfd_arg;
	g_clilen = clilen_arg;

	/* 初始化已接收数据队列 */
	for (i = 0; i < QSIZE; i++) {	/* init queue of buffers */
		dg[i].dg_data = malloc(MAXDG);
		dg[i].dg_sa = malloc(g_clilen);
		dg[i].dg_salen = g_clilen;
	}
	iget = iput = nqueue = 0;

	/* 建立信号处理函数并设置套接字标志 */
	Signal(SIGHUP, sig_hup); // SIGHUP信号用于诊断目的
	Signal(SIGIO, sig_io); // 1.为SIGIO信号建立的信号处理函数sig_io（SIGIO信号支持套接字的信号驱动式I/O）
	fcntl(g_sockfd, F_SETOWN, getpid()); // 2.设置套接字属主
	ioctl(g_sockfd, FIOASYNC, &on); // 3.开启该套接字的信号驱动式I/O
	ioctl(g_sockfd, FIONBIO, &on); // 既然信号是不排队的，开启信号驱动式I/O的描述符通常也设置为非阻塞式

	/* 初始化信号集 */
	sigemptyset(&zeromask);		/* init three signal sets */
	sigemptyset(&oldmask);
	sigemptyset(&newmask);
	sigaddset(&newmask, SIGIO);	/* signal we want to block */

	/* 阻塞SIGIO并等待有事可做 */
	sigprocmask(SIG_BLOCK, &newmask, &oldmask); // 阻塞SIGIO
	for ( ; ; ) {
		/* sigsuspend进行一下处理过程：
		 * 1.保存当前信号掩码；
		 * 2.设置当前信号掩码为zeromask，暂停进程执行，直到收到信号为止;
		 * 3.在进程捕获信号并且在该信号的处理函数返回之后才返回；
		 * 4.sigsuspend在该信号的处理函数返回之后才返回，并且将当前信号掩码恢复为调用时刻的值。 */
		while (nqueue == 0)
			sigsuspend(&zeromask); 

		/* 解阻塞SIGIO并发送应答 */
		sigprocmask(SIG_SETMASK, &oldmask, NULL);

		sendto(g_sockfd, dg[iget].dg_data, dg[iget].dg_len, 0, dg[iget].dg_sa, dg[iget].dg_salen);
		if (++iget >= QSIZE)
			iget = 0;

		/* 阻塞SIGIO以修改主循环和信号处理函数共同使用的值nqueue */
		sigprocmask(SIG_BLOCK, &newmask, &oldmask);
		nqueue--;
	}
}	
			
void dg_echo(int sockfd, struct sockaddr *pcliaddr, socklen_t clilen) {
	int n;
	socklen_t len;
	char mesg[MAXLINE];

	for ( ; ; ) {
		len = clilen;
		n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
		sendto(sockfd, mesg, n, 0, pcliaddr, len);
	}
}

int main(int argc, char **argv) {
	int sockfd;
	struct sockaddr_in servaddr, cliaddr;
	const int on = 1;

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 通配IP地址
	servaddr.sin_port = htons(SERV_PORT); 
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)); 
	bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	dg_echo_io(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));

	exit(0);
}


